/* ----------------------------------------------------------------------------
  buddy.cpp  -  generates pairing of Norwegian students (buddies) and foreigners
                using a Genetic Algorithm. This program was created from example 11
                in galib package.

  (c) 2002-2011, ISU Trondheim
---------------------------------------------------------------------------- */
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <string.h>
#include <ctype.h>
#include <ga/ga.h>
#include <mysql/mysql.h>

typedef struct buddy_foreign_str
{
  int id;
  char *name;
  char *email;
  char *tel;
  char *nationality;
  char *language1;
  char *language2;
  char gender;
  char wish_gender;
  int study;
  char *campus;
} buddy_foreign;

typedef struct buddy_norwegian_str
{
  int id;
  char *name;
  char *email;
  char *tel;
  char *language;
  char gender;
  char wish_gender;
  int study;
  char *campus;
} buddy_norwegian;

static buddy_foreign *foreigner;
static buddy_norwegian *buddy;
my_ulonglong n, real_n;
my_ulonglong m, real_m;
static float **match_value;
static char *season = "spring2005";   //which season
static int second = 0;
static int third, fourth;

// The objective function tells how good a genome is.  The Initializer defines
// how the lists should be initialized. The precompute function computes the
// match values for all possibile pairs
float objective(GAGenome &);
void ListInitializer(GAGenome &);
void precompute_match();

void toupperstr(char *s)
{
  s--;
  while (*(++s)) *s = toupper(*s);
}

int initialize_buddies()
{
  MYSQL buddy_db;
  MYSQL_RES *result;
  MYSQL_ROW row;
  unsigned long *lengths;
  int i, j;
  char query[200];
  double q_norm;


  // retrieve the information from the database to memory

  printf("Working in season %s\n", season);

  mysql_init(&buddy_db);

  if (0 == mysql_real_connect(&buddy_db, "***host***", "***user***", "***password***", "***database***", 0, 0, 0))
  {
    fprintf(stderr, "Cannot connect to the database, error: %s\n", mysql_error(&buddy_db));
    return 0;
  }

  sprintf(query, "SELECT * FROM buddy_foreign where (season=\"%s\") and (buddy_id=-1)", season);
  if (mysql_real_query(&buddy_db, query, strlen(query)))
  {
    fprintf(stderr, "Cannot retrieve foreigners from database, error: %s\n", mysql_error(&buddy_db));
    mysql_close(&buddy_db);
    return 0;
  }

  result = mysql_store_result(&buddy_db);

  real_n = n = mysql_num_rows(result);

  printf("loading %d foreigners...\n", n);

  foreigner = (buddy_foreign *)malloc(sizeof(buddy_foreign) * n);

  for (i = 0; i < n; i++)
  {
    row = mysql_fetch_row(result);
    lengths = mysql_fetch_lengths(result);

    foreigner[i].id = atoi(row[0]);
    foreigner[i].name = (char *)malloc(lengths[1] + 1);
    strcpy(foreigner[i].name, row[1]);
    foreigner[i].email = (char *)malloc(lengths[2] + 1);
    strcpy(foreigner[i].email, row[2]);
    foreigner[i].language1 = (char *)malloc(lengths[30] + 1);
    strcpy(foreigner[i].language1, row[30]);
    toupperstr(foreigner[i].language1);
    foreigner[i].language2 = (char *)malloc(lengths[31] + 1);
    strcpy(foreigner[i].language2, row[31]);
    toupperstr(foreigner[i].language2);
    foreigner[i].gender = (char)row[4][0];
    foreigner[i].wish_gender = (char)row[25][0];

    sscanf(row[7], "%d", &foreigner[i].study);
    foreigner[i].campus = (char *)malloc(lengths[29] + 1);
    strcpy(foreigner[i].campus, row[29]);
    foreigner[i].tel = (char *)malloc(lengths[14] + 1);
    strcpy(foreigner[i].tel, row[14]);

  }

  //printf("--------------------------------------------------------------------------\n\n");

  mysql_free_result(result);

  sprintf(query, "SELECT * FROM buddy_norwegian where season=\"%s\"", season);
  if (mysql_real_query(&buddy_db, query, strlen(query)))
  {
    fprintf(stderr, "Cannot retrieve norwegians from database, error: %s\n", mysql_error(&buddy_db));
    mysql_close(&buddy_db);
    return 0;
  }

  result = mysql_store_result(&buddy_db);

  real_m = m = mysql_num_rows(result);

  printf("loading %d Norwegians...\n", m);

  buddy = (buddy_norwegian *)malloc(sizeof(buddy_norwegian) * m);

  for (i = 0; i < m; i++)
  {
    row = mysql_fetch_row(result);
    lengths = mysql_fetch_lengths(result);

    buddy[i].id = atoi(row[0]);
    buddy[i].name = (char *)malloc(lengths[1] + 1);
    strcpy(buddy[i].name, row[1]);
    buddy[i].email = (char *)malloc(lengths[2] + 1);
    strcpy(buddy[i].email, row[2]);
    buddy[i].language = (char *)malloc(lengths[40] + 1);
    strcpy(buddy[i].language, row[40]);
    toupperstr(buddy[i].language);
    buddy[i].gender = row[6][0];
    buddy[i].wish_gender = row[10][0];
    sscanf(row[8], "%d", &buddy[i].study);
    buddy[i].campus = (char *)malloc(lengths[37] + 1);
    strcpy(buddy[i].campus, row[37]);
    buddy[i].tel = (char *)malloc(lengths[17] + 1);
    strcpy(buddy[i].tel, row[17]);
  }

  mysql_free_result(result);

  mysql_close(&buddy_db);

  static char *no = "<not available>";

  printf("hello\n");
  /* if there is not the same number of buddies and norwegians, create fake persons... */
  if (n > m)  /* more foreigners, crate fake buddies */
  {
     printf("%lld > %lld -> creating fake budies...\n", n, m);

     buddy = (buddy_norwegian *)realloc(buddy, sizeof(buddy_norwegian) * n);
     for (; m < n; m++)
     {
       buddy[m].id = -1;
       buddy[m].name = buddy[m].email = buddy[m].language = buddy[m].tel = no;
       buddy[m].study = 0;
       buddy[m].gender = 'X';
       buddy[m].wish_gender = 'X';
       buddy[m].campus = "X";
     }
  }
  else if (n < m)  /* more buddies, create fake foreigners */
  {
    printf("%lld < %lld -> creating fake foreigners\n", n, m);

    foreigner = (buddy_foreign *)realloc(foreigner, sizeof(buddy_foreign) * m);
    for (; n < m; n++)
    {
      foreigner[n].id = -1;
      foreigner[n].name = foreigner[n].email = foreigner[n].language1 = foreigner[n].language2 = foreigner[n].tel = no;
      foreigner[n].gender = 'X';
      foreigner[n].wish_gender = 'X';
      foreigner[n].study = 0;
      foreigner[n].campus = "X";
    }
  }


  /* initialize and precompute the match values */
  match_value = (float **)malloc(n * sizeof(float *));
  for (i = 0; i < n; i++)
    match_value[i] = (float *)malloc(n * sizeof(float));

  printf("precomputing matching values for all possible pairs...\n");
  precompute_match();
  printf("initialization done...\n");

  return 1;
}

/* write the result to the database */
void write_result(ostream &f, GAGenome &c)
{
  GAListGenome<int> & genome = (GAListGenome<int> &)c;
  MYSQL buddy_db;
  MYSQL_RES *result;
  MYSQL_ROW row;
  unsigned long *lengths;
  int i;
  char query[100];
  const char *seconds = (second)?"2":"";
  if (third) seconds = "3";
  if (fourth) seconds = "4";

  // retrieve the information from the database to memory

  //mysql_init(&buddy_db);

  /*if (0 == mysql_real_connect(&buddy_db, "mysql.stud.ntnu.no", "isu_admin", "syitb24", "isu", 0, 0, 0))
  {
    fprintf(stderr, "Cannot connect to the database, error: %s\n", mysql_error(&buddy_db));
    return;
  }
*/
  int *bb = genome.head();
  if (!bb)
  {
    f << "genome empty!\n";
    return;
  }
  int b = *bb;

  printf("printing result, size=%d\n", n);
  for (i = 0; i < n; i++)
  {
    if ((buddy[b].id != -1) && (foreigner[i].id != -1))
    {
      sprintf(query, "UPDATE buddy_foreign SET buddy_id=%d where id=%d", buddy[b].id, foreigner[i].id);
      f << query << ";\n";
      sprintf(query, "UPDATE buddy_norwegian SET guest_id%s=%d where id=%d", seconds, foreigner[i].id, buddy[b].id);
      f << query << ";\n";
    }
/*
    if (mysql_real_query(&buddy_db, query, strlen(query)))
    {
      fprintf(stderr, "Error updating database, record#%d: %s\n", i, mysql_error(&buddy_db));
      //mysql_close(&buddy_db);
      //return;
    }
*/
    if (i < n - 1) b = *genome.next();
  }

  //mysql_close(&buddy_db);
}

char *stripnl(char *s)
{
  char *s0 = s;
  s--;
  while (*(++s))
    if (*s == '\n') *s = ' ';
  return s0;
}

GASteadyStateGA *garef;
//GADemeGA *garef;

int
main(int argc, char *argv[])
{
  int numgen = 60000;

// See if we've been given a seed to use (for testing purposes).  When you
// specify a random seed, the evolution will be exactly the same each time
// you use that seed number.

  second = third = fourth = 0;
  for(int ii=1; ii<argc; ii++) {
    if(strcmp(argv[ii],"seed") == 0) {
      GARandomSeed((unsigned int)atoi(argv[++ii]));
    }
    if(strcmp(argv[ii],"season") == 0)
      season = argv[++ii];
    if(strcmp(argv[ii],"first") == 0)
      second=0;
    if(strcmp(argv[ii],"second") == 0)
      second=1;
    if(strcmp(argv[ii],"third") == 0)
      third=1;
    if(strcmp(argv[ii],"fourth") == 0)
      fourth=1;
    if(strcmp(argv[ii],"numgen") == 0)
      sscanf(argv[++ii], "%d", &numgen);
  }

  //load information about buddies into memory
  if (!initialize_buddies()) return 1;

  printf("setting up ga parameters...\n");

// Set the default values of the parameters.

  GAParameterList params;
  GASteadyStateGA::registerDefaultParameters(params);
//  GADemeGA::registerDefaultParameters(params);
//  params.set(gaNpopulationSize, 80);	// population size
  params.set(gaNpopulationSize, 250);	// population size
  params.set(gaNpCrossover, 0.12);      // probability of crossover
  params.set(gaNpMutation, 0.05);	// probability of mutation
  params.set(gaNnGenerations, numgen);	// number of generations
  params.set(gaNpReplacement, 0.5);	// how much of pop to replace each gen
  params.set(gaNscoreFrequency, 5);	// how often to record scores
  //params.set(gaNnReplacement, 15);	// how much of pop to replace each gen
  params.set(gaNnReplacement, 125);	// how much of pop to replace each gen
  params.set(gaNflushFrequency, 1);	// how often to dump scores to file
  params.set(gaNscoreFilename, "bog.dat");
//  params.read("settings.txt");	        // grab values from file first
  params.parse(argc, argv, gaFalse); // parse command line for GAlib args

// Now create the GA and run it.  We first create a genome with the
// operators we want.  Since we're using a template genome, we must assign
// all three operators.  We use the order-based crossover site when we assign
// the crossover operator.

  GAListGenome<int> genome(objective);
  genome.initializer(ListInitializer);
  genome.mutator(GAListGenome<int>::SwapMutator);

// Now that we have our genome, we create the GA (it clones the genome to
// make all of the individuals for its populations).  Set the parameters on
// the GA then let it evolve.

  GASteadyStateGA ga(genome);

//  GADemeGA ga(genome);
  garef = &ga;

  ga.crossover(GAListGenome<int>::PartialMatchCrossover);
  ga.parameters(params);
//  ga.nPopulations(40);
//  ga.nMigration(5);

/*  while(!ga.done()){
    ga.step();
    int gn = ga.generation();
    cout << "\010" << gn << " of " << numgen << " (" << (int)(100.0 * (float)gn / (float)numgen) << " %)                 ";

    cout.flush();
  }
*/
  printf("evolving %d generations, genome size=%d...\n", numgen, n);
  ga.evolve();

// Assign the best that the GA found to our genome then print out the results.

  genome = ga.statistics().bestIndividual();
  cout << "the ga generated the following list (objective score is ";
  cout << genome.score() << "):\n" << genome << "\n";
  cout << "best of generation data are in '" << ga.scoreFilename() << "'\n";
  cout << ga.parameters() << "\n";

  // apply the hill-climbing on the final genome

  double lastfit = 0.0;
  double curfit = objective(genome);
  double wasfit = curfit;
  double testfit;
  int i, j, swap_i, swap_j;

  while (lastfit != curfit)
  {
    lastfit = curfit;
    for (i = 0; i < n; i++)
     for (j = i + 1; j < n; j++)
     {
       genome.swap(i, j);
       testfit = objective(genome);
       if (testfit > curfit)
       {
         curfit = testfit;
         swap_i = i;
         swap_j = j;
       }
       genome.swap(i, j);
     }
    if (curfit > lastfit) genome.swap(swap_i, swap_j);
  }

  cout << "hill-climbing with swap(i,j) improved the final fitness from " << wasfit << " to " << curfit << ".\n";

  ofstream f;
  f.open("buddy.out");

  f << "\nForeigners -> Buddies\nID, NAME, E-MAIL, LANGUAGE1, LANGUAGE2, GENDER, STUDY, CAMPUS\n";
  f << "ID, NAME, E-MAIL, LANGUAGE, WISH_GENDER, STUDY, CAMPUS\n\n";

  j = *genome.head();
  for (int i = 0; i < n; i++)
  {
    f << foreigner[i].id << ", " << foreigner[i].name << "[" << foreigner[i].email << "] " <<
            foreigner[i].language1 << ", " << foreigner[i].language2 << ", " << foreigner[i].gender << ", " << foreigner[i].study << ", " <<
            foreigner[i].campus << "\n";
    f << buddy[j].id << ", " << buddy[j].name << "[" << buddy[j].email << "] " <<
            buddy[j].language << ", " <<
            buddy[j].wish_gender << ", " << buddy[j].study << ", " << buddy[j].campus << "\n";
    f << "match_value: " << match_value[i][j] << "\n";
    f << "----------------------------------------------------------------------------------\n";
    if (i < n - 1) j = *genome.next();
  }
  f.flush();
  f << "commands to update the database:\n";
  f.flush();
  write_result(f, genome);
  f.close();

//  char *fn;
//  ga.get(gaNscoreFilename, &fn);
//  cout << "filename is '" << fn << "'\n";

  return 0;
}



/* ----------------------------------------------------------------------------
Objective function
  - assigns points based on country, gender and interest matches
---------------------------------------------------------------------------- */

int gender_match(char g1, char g2)
{
  if (g1 == 'X') return 0;
  if (g2 == 'X') return 0;
  if (g1 == g2) return 1;
  if (g1 == 'A') return 1;
  if (g2 == 'A') return 1;
  return 0;
}

int study_match(int s1, int s2)
{
  if (s1 == s2) return 1;
  return 0;
}


float
objective(GAGenome & c)
{
  float score = 0.0;
  GAListGenome<int> & genome = (GAListGenome<int> &)c;
  static int genn = 0;
  int i;

  int gn = garef->generation();
  if (gn != genn)
  {
    if (gn % 100 == 0) cout << gn << ';';
    cout.flush();
    genn = gn;
  }
/*
  if ((gn >= 10000) && (gn < 10100)) return 4000.0;
  else if ((gn >= 15000) && (gn < 15100)) return 4000.0;
  else if ((gn == 10100) || (gn == 15100))
  {
    for (i = 0; i < garef->populationSize(); i++)
      garef->population().individual(i).evaluate(gaTrue);
  }
*/

  int *bb = genome.head();
  if (!bb) return score;

  int b = *bb;

  for(int f=0; f<n; f++)
  {
    score += match_value[f][b];
    if (f < n - 1) b = *genome.next();
  }

/*  if (garef->generation() == 12000) garef->populationSize(-1, 120); */
//  if (garef->generation() == 15000) garef->pMutation(0.02);

  return score;
}

void precompute_match()
{
  int b, f, cm;
  float score;

  for (b = 0; b < n; b++)
  {
    for (f = 0; f < n; f++)
    {
      score = 0.0;

      if (strcasecmp(buddy[b].language, foreigner[f].language1) == 0) score += 15;
      else if (strcasecmp(buddy[b].language, foreigner[f].language2) == 0) score += 12;

      if (gender_match(buddy[b].wish_gender, foreigner[f].gender)) score += 3;
      if (gender_match(buddy[b].gender, foreigner[f].wish_gender)) score += 3;

      if (study_match(buddy[b].study, foreigner[f].study)) score += 3;

      if (strcmp(buddy[b].campus, foreigner[f].campus) == 0) score += 5;

      match_value[f][b] = score;
    }
  }
}

/* ----------------------------------------------------------------------------
List Genome Operators
-------------------------------------------------------------------------------
The initializer creates a list with n elements in it and puts a unique digit
in each one.  After we make the list, we scramble everything up.
---------------------------------------------------------------------------- */
void
ListInitializer(GAGenome & c)
{
  GAListGenome<int> &child=(GAListGenome<int> &)c;
  while(child.head()) child.destroy(); // destroy any pre-existing list

  child.insert(0,GAListBASE::HEAD); // the head node contains a '0'
  for(int i=1; i<n; i++)
    child.insert(i);		// each subsequent node contains a number
  for(int j=0; j<n; j++)
    child.swap(j, GARandomInt(0,n-1));
  for(int j=0; j<2 * n; j++)
    child.swap(GARandomInt(0,n-1), GARandomInt(0,n-1));
}




//   Here we specialize the write method for the List class.  This lets us see
// exactly what we want (the default write method dumps out pointers to the
// data rather than the data contents).
//   This routine prints out the contents of each element of the list,
// separated by a space.  It does not put a newline at the end of the list.
//   Notice that you can specialize ANY function of a template class, but
// some compilers are more finicky about how you do it than others.  For the
// metrowerks compiler this specialization must come before the forced
// instantiation.
int
GAListGenome<int>::write(ostream & os) const
{
  int *cur, *head;
  GAListIter<int> tmpiter(*this);
  if((head=tmpiter.head()) != 0) os << *head << " ";
  for(cur=tmpiter.next(); cur && cur != head; cur=tmpiter.next())
    os << *cur << " ";
  os << "hello\n";
  return os.fail() ? 1 : 0;
}




// If your compiler does not do automatic instantiation (e.g. g++ 2.6.8),
// then define the NO_AUTO_INST directive.
#ifdef NO_AUTO_INST
#include <ga/GAList.C>
#include <ga/GAListGenome.C>
#if defined(__GNUG__)
template class GAList<int>;
template class GAListGenome<int>;
#else
GAList<int>;
GAListGenome<int>;
#endif
#endif
